package com.distribute.core.core.el;

import cn.hutool.core.util.StrUtil;
import com.distribute.core.constants.CacheConstants;
import com.distribute.core.enums.NotifyFlushType;
import com.distribute.core.notify.DLCNotify;
import com.distribute.core.utils.SpringContextUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.lang.reflect.Field;
import java.util.Objects;


@Aspect
public class DLCNotifyAspect implements BeanFactoryAware {

    private static final Logger log = LoggerFactory.getLogger(DLCNotifyAspect.class);

    private ExpressionEvaluator<String> evaluator = new ExpressionEvaluator<>();


    private ResultExpressionEvaluator evaluatorResult = new ResultExpressionEvaluator();

    private BeanFactory beanFactory;

    private static final String RESULT_KEY_EVA = "#result";

    @Around(value = "@annotation(dlcUpdateNotify)")
    public Object handlerControllerMethod(ProceedingJoinPoint joinPoint, DLCUpdateNotify dlcUpdateNotify) throws Throwable {
        Object proceedResult = joinPoint.proceed();
        transactionProcessAfterCommit(() -> notifyCacheUpdate(proceedResult, joinPoint, dlcUpdateNotify));
        return proceedResult;
    }

    public void notifyCacheUpdate(Object proceedResult, ProceedingJoinPoint joinPoint, DLCUpdateNotify dlcUpdateNotify) {
        DLCNotify notifyCacheUpdateImpl = SpringContextUtils.getBean(CacheConstants.REDIS_NOTIFY_COMPONENT_NAME, DLCNotify.class);
        NotifyFlushType notifyFlushType = dlcUpdateNotify.notifyFlushType();
        String modelPrefixName = dlcUpdateNotify.modelPrefix();
        String[] modelArr = dlcUpdateNotify.model();
        String key = dlcUpdateNotify.key();
        // 解析注解 中 key 的 表达式的值
        String value;
        if (key.contains(RESULT_KEY_EVA)){
            value = getAttachmentIdResult(dlcUpdateNotify, joinPoint, proceedResult);
        }else {
            value = getAttachmentId(dlcUpdateNotify, joinPoint);
        }

        for (String modelName : modelArr) {
            if (notifyFlushType.equals(NotifyFlushType.ALL)) {
                notifyCacheUpdateImpl.notifyCacheUpdateAll(modelPrefixName, modelName);
            } else if (notifyFlushType.equals(NotifyFlushType.PART)) {
                notifyCacheUpdateImpl.notifyCacheUpdate(modelPrefixName, modelName, value);
            }
        }
    }

    private String getAttachmentId(DLCUpdateNotify dlcUpdateNotify, JoinPoint joinPoint) {
        if (joinPoint.getArgs() == null) {
            return StrUtil.EMPTY;
        }

        EvaluationContext evaluationContext = evaluator.createEvaluationContext(joinPoint.getTarget(), joinPoint.getTarget().getClass(),
                ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs());
        AnnotatedElementKey methodKey = new AnnotatedElementKey(((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getTarget().getClass());
        return evaluator.condition(dlcUpdateNotify.key(), methodKey, evaluationContext, String.class);
    }

    private String getAttachmentIdResult(DLCUpdateNotify dlcUpdateNotify, JoinPoint joinPoint, Object proceedResult) {
        if (joinPoint.getArgs() == null) {
            return StrUtil.EMPTY;
        }

        EvaluationContext evaluationContext = evaluatorResult.createEvaluationContext(joinPoint.getTarget(), joinPoint.getTarget().getClass(), ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs(), proceedResult, beanFactory);
        AnnotatedElementKey methodKey = new AnnotatedElementKey(((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getTarget().getClass());
       return evaluatorResult.condition(dlcUpdateNotify.key(), methodKey, evaluationContext);
    }

    private static void transactionProcessAfterCommit(AfterCommitDoSomething afterCommitDoSomething) {
        // 判断事务激活，如果激活，则按这个方法执行
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronization transactionSynchronization = new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    afterCommitDoSomething.run();
                }
            };

            TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
        } else {
            // 如果事务没有激活，则直接执行
            afterCommitDoSomething.run();
        }
    }

    /**
     * 返回值结果解析 使用方式 #result => 直接获取返回值 ，  #result.name => 获取对象属性 老王，  #result.name + ':' + #result.age => 老王:18
     *
     * @param key
     * @param result
     * @return
     */
    public String getResultValue(String key, Object result) {
        if (Objects.isNull(result)) {
            return StrUtil.EMPTY;
        }

        if (key.equals(RESULT_KEY_EVA)) {
            return String.valueOf(result);
        }

        key = StrUtil.trim(key);
        key = key.replaceAll(StrUtil.SPACE, StrUtil.EMPTY);
        String[] keyArr = key.trim().split("\\+");
        StringBuilder valueResult = new StringBuilder();
        try {
            for (String keyStr : keyArr) {
                if (!keyStr.contains("#")) {
                    keyStr = keyStr.replaceAll("\\'", StrUtil.EMPTY);
                    valueResult.append(keyStr);
                    continue;
                }

                String[] valueArr = keyStr.split("\\.");
                for (int i = 0; i < valueArr.length - 1; i++) {
                    Field declaredField = result.getClass().getDeclaredField(valueArr[i + 1]);
                    declaredField.setAccessible(true);
                    valueResult.append(declaredField.get(result));
                }
            }

            return valueResult.toString();
        } catch (Exception e) {
            log.error("本地缓存通知更新解析返回表达式失败！！！" + e.getMessage() + " key: " + key);
        }

        return StrUtil.EMPTY;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @FunctionalInterface
    public interface AfterCommitDoSomething {
        void run();
    }
}